﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using DVK.SP2007;
namespace DVK.SP2007.UniversalClient
{
    /// <summary>
    /// Helps manage Dvk MetaXml element in Dvk message.
    /// 
    /// <remarks>
    /// &lt;dhl:metaxml&gt;
    /// </remarks>
    /// 
    /// Schemas:
    /// http://www.riik.ee/schemas/dhl/rkel_letter
    /// 
    /// </summary>
    public class DvkMetaXmlPart
    {
        private XElement _metaXml;

        /// <summary>
        /// Gets the element.
        /// </summary>
        /// <value>The element.</value>
        public XElement Element
        {
            get
            {
                return _metaXml;
            }
        }

        public const string MetaXmlNamespace = "http://www.riik.ee/schemas/dhl/rkel_letter";

        #region ElementNames
        public static readonly XName ElementNameMetaXml = XName.Get("metaxml", DvkXml.RootNamespace);
        public static readonly XName ElementNameAddressees = GetName("Addressees");
        public static readonly XName ElementNameAddressee = GetName("Addressee");
        public static readonly XName ElementNameAuthor = GetName("Author");

        public static readonly XName ElementNameSignatures = GetName("Signatures");
        public static readonly XName ElementNameSignature = GetName("Signature");
        public static readonly XName ElementNameSignatureData = GetName("SignatureData");
        public static readonly XName ElementNameSignatureDate = GetName("SignatureDate");
        public static readonly XName ElementNameSignatureTime = GetName("SignatureTime");

        public static readonly XName ElementNamePerson = GetName("Person");
        public static readonly XName ElementNamePersonFirstname = GetName("firstname");
        public static readonly XName ElementNamePersonSurname = GetName("surname");
        public static readonly XName ElementNamePersonJobtitle = GetName("jobtitle");
        public static readonly XName ElementNamePersonEmail = GetName("email");
        public static readonly XName ElementNamePersonTelephone = GetName("telephone");

        public static readonly XName ElementNameOrganisation = GetName("Organisation");
        public static readonly XName ElementNameOrganisationName = GetName("organisationName");
        public static readonly XName ElementNameOrganisationDepartmentName = GetName("departmentName");

        public static readonly XName ElementNameCompilators = GetName("Compilators");
        public static readonly XName ElementNameCompilator = GetName("Compilator");

        public static readonly XName ElementNameLetterMetadata = GetName("LetterMetaData");
        public static readonly XName ElementNameLetterMetadataSignDate = GetName("SignDate");
        public static readonly XName ElementNameLetterMetadataSenderIdentifier = GetName("SenderIdentifier");
        public static readonly XName ElementNameLetterMetadataOriginalIdentifier = GetName("OriginalIdentifier");
        public static readonly XName ElementNameLetterMetadataType = GetName("Type");
        public static readonly XName ElementNameLetterMetadataLanguange = GetName("Languange");
        public static readonly XName ElementNameLetterMetadataVersion = GetName("Version");
        public static readonly XName ElementNameLetterMetadataTitle = GetName("Title");
        public static readonly XName ElementNameLetterMetadataDeadline = GetName("Deadline");
        public static readonly XName ElementNameLetterMetadataEnclosures = GetName("Enclosures");
        public static readonly XName ElementNameLetterMetadataEnclosureTitle = GetName("EnclosureTitle");
        public static readonly XName ElementNameLetterMetadataAccessRights = GetName("AccessRights");
        public static readonly XName ElementNameLetterMetadataAccessRightRestriction = GetName("Restriction");
        public static readonly XName ElementNameLetterMetadataAccessRightBeginDate = GetName("BeginDate");
        public static readonly XName ElementNameLetterMetadataAccessRightEndDate = GetName("EndDate");
        public static readonly XName ElementNameLetterMetadataAccessRightReason = GetName("Reason");
        public static readonly XName ElementNameLetterMetadataIntellectualPropertyRights = GetName("IntellectualPropertyRights");
        public static readonly XName ElementNameLetterMetadataIntellectualPropertyRightCopyrightEndDate = GetName("CopyrightEndDate");
        public static readonly XName ElementNameLetterMetadataIntellectualPropertyRightIprOwner = GetName("IprOwner");

        public static readonly XName ElementNameLetterMetadataNoCopies = GetName("NoCopies");
        public static readonly XName ElementNameLetterMetadataSenderSeriesTitle = GetName("SenderSeriesTitle");
        public static readonly XName ElementNameLetterMetadataSenderFolderTitle = GetName("SenderFolderTitle");
        public static readonly XName ElementNameLetterMetadataSenderVitalRecordIndicator = GetName("SenderVitalRecordIndicator");
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="DvkMetaXmlPart"/> class.
        /// 
        /// Creates a new empty MetaXml element.
        /// </summary>
        public DvkMetaXmlPart() : this(new XElement(ElementNameMetaXml)) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="DvkMetaXmlPart"/> class.
        /// 
        /// Creates a new MetaXml element using the element data inputed. 
        /// If it is empty, creates a empty structure of required elements.
        /// </summary>
        /// <param name="metaXml">The meta XML.</param>
        public DvkMetaXmlPart(XElement metaXml)
        {
            _metaXml = metaXml;
            //This is a new element
            if (_metaXml.IsEmpty)
            {
                _metaXml.Add(
                    new XElement(ElementNameAddressees),
                    new XElement(ElementNameSignatures),
                    new XElement(ElementNameAuthor),
                    new XElement(ElementNameCompilators),
                    GetLetterMetadataWithRequiredElements()
                    );
            }
        }

        /// <summary>
        /// Gets the letter metadata with required elements.
        /// </summary>
        /// <returns></returns>
        private XElement GetLetterMetadataWithRequiredElements()
        {
            //These elements must be added, so that when mapping is looking for parents, 
            //it will always find them and be able to add children. Its impossible for the mapper 
            //to know where the parent should be located to add it automatically.
            return new XElement(
                    ElementNameLetterMetadata,
                    new XElement(ElementNameLetterMetadataAccessRights),
                    new XElement(ElementNameLetterMetadataEnclosures),
                    new XElement(ElementNameLetterMetadataIntellectualPropertyRights)
                );
        }

        /// <summary>
        /// Adds signature element to metaxml element.
        /// </summary>
        /// <param name="firstname">The firstname.</param>
        /// <param name="surname">The surname.</param>
        /// <param name="jobtitle">The jobtitle.</param>
        /// <param name="email">The email.</param>
        /// <param name="telephone">The telephone.</param>
        /// <param name="signatureDate">The signature date.</param>
        public void AddSignature(string firstname, string surname, string jobtitle, string email, string telephone, DateTime? signatureDate)
        {
            var signatures = AddOrGetElement(ElementNameSignatures);

            signatures.Add(
                    new XElement(ElementNameSignature,
                        CreatePersonTypeElement(ElementNamePerson, firstname, surname, jobtitle, email, telephone),
                        new XElement(ElementNameSignatureData,
                                new XElement(ElementNameSignatureDate, XmlHelper.GetISO8601Date(signatureDate)),
                                new XElement(ElementNameSignatureTime, XmlHelper.GetISO8601Time(signatureDate))
                            )
                    )
                );
        }

        /// <summary>
        /// Adds compilators to metaxml element.
        /// </summary>
        /// <param name="personMetadatas">The person metadatas.</param>
        public void AddCompilators(List<PersonMetadata> personMetadatas)
        {
            foreach (var person in personMetadatas)
            {
                AddCompilator(person);
            }
        }

        /// <summary>
        /// Adds one compilator info to metaxml element.
        /// </summary>
        /// <param name="personMetadata">The person metadata.</param>
        public void AddCompilator(PersonMetadata personMetadata)
        {
            AddCompilator(personMetadata.FirstName, personMetadata.SurName, personMetadata.JobTitle, personMetadata.Email, personMetadata.Telephone);
        }

        /// <summary>
        /// Adds compilator to metaxml element.
        /// </summary>
        /// <param name="firstname">The firstname.</param>
        /// <param name="surname">The surname.</param>
        /// <param name="jobtitle">The jobtitle.</param>
        /// <param name="email">The email.</param>
        /// <param name="telephone">The telephone.</param>
        public void AddCompilator(string firstname, string surname, string jobtitle, string email, string telephone)
        {
            var compilators = AddOrGetElement(ElementNameCompilators);

            compilators.Add(
                    CreatePersonTypeElement(ElementNameCompilator, firstname, surname, jobtitle, email, telephone)
                );
        }

        /// <summary>
        /// Generic method to update or add any element in the metaxml.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="data">The data.</param>
        public void AddOrUpdateLetterMetadataElement<T>(XName elementName, T data)
        {
            var letterMetadata = AddOrGetElement(ElementNameLetterMetadata);
            AddOrUpdateElement<T>(letterMetadata, elementName, data);
        }

        /// <summary>
        /// Method to add or update required elements in the metaxml.
        /// </summary>
        /// <param name="signDate">The sign date.</param>
        /// <param name="senderIdentifier">The sender identifier.</param>
        /// <param name="type">The type.</param>
        /// <param name="language">The language.</param>
        /// <param name="title">The title.</param>
        public void AddOrUpdateLetterMetadata(DateTime? signDate, string senderIdentifier, string type, string language, string title)
        {
            var letterMetadata = AddOrGetElement(ElementNameLetterMetadata);

            AddOrUpdateElement<string>(letterMetadata, ElementNameLetterMetadataSignDate, XmlHelper.GetISO8601DateTime(signDate));
            AddOrUpdateElement<string>(letterMetadata, ElementNameLetterMetadataSenderIdentifier, senderIdentifier);
            AddOrUpdateElement<string>(letterMetadata, ElementNameLetterMetadataType, type);
            AddOrUpdateElement<string>(letterMetadata, ElementNameLetterMetadataTitle, title);
            AddOrUpdateElement<string>(letterMetadata, ElementNameLetterMetadataLanguange, language);
        }

        /// <summary>
        /// Adds access right information to metaxml element.
        /// </summary>
        /// <param name="restriction">The restriction.</param>
        /// <param name="reason">The reason.</param>
        /// <param name="beginDate">The begin date.</param>
        /// <param name="endDate">The end date.</param>
        public void AddAccessRight(string restriction, string reason, DateTime beginDate, DateTime endDate)
        {
            var letterMetadata = AddOrGetElement(ElementNameLetterMetadata);
            var accessRights = AddOrGetElement(letterMetadata, ElementNameLetterMetadataAccessRights);

            AddOrUpdateElement<string>(accessRights, ElementNameLetterMetadataAccessRightRestriction, restriction);
            AddOrUpdateElement<string>(accessRights, ElementNameLetterMetadataAccessRightReason, reason);
            AddOrUpdateElement<string>(accessRights, ElementNameLetterMetadataAccessRightBeginDate, XmlHelper.GetISO8601Date(beginDate));
            AddOrUpdateElement<string>(accessRights, ElementNameLetterMetadataAccessRightEndDate, XmlHelper.GetISO8601Date(endDate));
        }

        /// <summary>
        /// Adds intellectgual property right information to metaxml element..
        /// </summary>
        /// <param name="copyrightEndDate">The copyright end date.</param>
        /// <param name="iprOwner">The ipr owner.</param>
        public void AddIntellectgualPropertyRight(DateTime copyrightEndDate, string iprOwner)
        {
            var letterMetadata = AddOrGetElement(ElementNameLetterMetadata);
            var intellectualPropertyRights = AddOrGetElement(letterMetadata, ElementNameLetterMetadataIntellectualPropertyRights);

            AddOrUpdateElement<string>(intellectualPropertyRights, ElementNameLetterMetadataIntellectualPropertyRightCopyrightEndDate, XmlHelper.GetISO8601Date(copyrightEndDate));
            AddOrUpdateElement<string>(intellectualPropertyRights, ElementNameLetterMetadataIntellectualPropertyRightIprOwner, iprOwner);
        }

        /// <summary>
        /// Adds enclosure information to metaxml.
        /// </summary>
        /// <param name="title">The title.</param>
        public void AddEnclosure(string title)
        {
            var letterMetadata = AddOrGetElement(ElementNameLetterMetadata);
            var enclosures = AddOrGetElement(letterMetadata, ElementNameLetterMetadataEnclosures);

            enclosures.Add(
                    new XElement(
                        ElementNameLetterMetadataEnclosureTitle,
                        title)
                );
        }

        /// <summary>
        /// Adds addressee to metaxml.
        /// </summary>
        /// <param name="recipient">The recipient.</param>
        public void AddAddressee(SenderReceiverInfo recipient)
        {
            var firstName = string.Empty;
            var lastName = string.Empty;
            if (recipient.Person != null)
            {
                firstName = (string.IsNullOrEmpty(recipient.Person.Name) ? string.Empty : recipient.Person.Name.Remove(recipient.Person.Name.IndexOf(" ")));
                lastName = (string.IsNullOrEmpty(recipient.Person.Name) ? string.Empty : recipient.Person.Name.Replace(firstName + " ", ""));
            }
            AddAddressee(
                    recipient.Organisation.Name,
                    (recipient.OrganisationUnit == null ? null : recipient.OrganisationUnit.Name),
                    firstName,
                    lastName,
                    recipient.JobTitle,
                    recipient.Email,
                    recipient.Telephone
                    );
        }

        /// <summary>
        /// Adds addressee to metaxml.
        /// </summary>
        /// <param name="organisationName">Name of the organisation.</param>
        /// <param name="departmentName">Name of the department.</param>
        /// <param name="firstname">The firstname.</param>
        /// <param name="surname">The surname.</param>
        /// <param name="jobtitle">The jobtitle.</param>
        /// <param name="email">The email.</param>
        /// <param name="telephone">The telephone.</param>
        public void AddAddressee(string organisationName, string departmentName, string firstname, string surname, string jobtitle, string email, string telephone)
        {
            var addressees = AddOrGetElement(ElementNameAddressees);

            addressees.Add(
                    CreatePartyTypeElement(ElementNameAddressee, organisationName, departmentName, firstname, surname, jobtitle, email, telephone)
                );
        }

        /// <summary>
        /// Adds author to metaxml.
        /// </summary>
        /// <param name="organisationName">Name of the organisation.</param>
        /// <param name="personMetadata">The person metadata.</param>
        public void AddAuthor(string organisationName, PersonMetadata personMetadata, bool useAuthorOrganisation)
        {
            if (personMetadata == null && !useAuthorOrganisation)
                return;

            if (useAuthorOrganisation && personMetadata != null)
            {
                organisationName = string.IsNullOrEmpty(personMetadata.OrganisationName) ? organisationName : personMetadata.OrganisationName;
            }

            AddAuthor(
                    organisationName,
                    personMetadata == null ? null : personMetadata.DepartmentName,
                    personMetadata == null ? null : personMetadata.FirstName,
                    personMetadata == null ? null : personMetadata.SurName,
                    personMetadata == null ? null : personMetadata.JobTitle,
                    personMetadata == null ? null : personMetadata.Email,
                    personMetadata == null ? null : personMetadata.Telephone
                );
        }

        /// <summary>
        /// Adds author to metaxml.
        /// </summary>
        /// <param name="organisationName">Name of the organisation.</param>
        /// <param name="departmentName">Name of the department.</param>
        /// <param name="firstname">The firstname.</param>
        /// <param name="surname">The surname.</param>
        /// <param name="jobtitle">The jobtitle.</param>
        /// <param name="email">The email.</param>
        /// <param name="telephone">The telephone.</param>
        public void AddAuthor(string organisationName, string departmentName, string firstname, string surname, string jobtitle, string email, string telephone)
        {
            var author = _metaXml.Element(ElementNameAuthor);
            if (author != null)
                author.Remove();
            _metaXml.Add(
                    CreatePartyTypeElement(ElementNameAuthor, organisationName, departmentName, firstname, surname, jobtitle, email, telephone)
                );
        }

        /// <summary>
        /// Creates the party type element.
        /// </summary>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="organisationName">Name of the organisation.</param>
        /// <param name="departmentname">The departmentname.</param>
        /// <param name="firstname">The firstname.</param>
        /// <param name="surname">The surname.</param>
        /// <param name="jobtitle">The jobtitle.</param>
        /// <param name="email">The email.</param>
        /// <param name="telephone">The telephone.</param>
        /// <returns></returns>
        private XElement CreatePartyTypeElement(XName elementName, string organisationName, string departmentname, string firstname, string surname, string jobtitle, string email, string telephone)
        {
            var element = new XElement(
                    elementName,
                    new XElement(ElementNameOrganisation,
                        new XElement(ElementNameOrganisationName, organisationName),
                        new XElement(ElementNameOrganisationDepartmentName, departmentname)
                        ),
                    CreatePersonTypeElement(ElementNamePerson, firstname, surname, jobtitle, email, telephone)
                );
            return element;
        }

        /// <summary>
        /// Creates the person type element.
        /// </summary>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="firstname">The firstname.</param>
        /// <param name="surname">The surname.</param>
        /// <param name="jobtitle">The jobtitle.</param>
        /// <param name="email">The email.</param>
        /// <param name="telephone">The telephone.</param>
        /// <returns></returns>
        private XElement CreatePersonTypeElement(XName elementName, string firstname, string surname, string jobtitle, string email, string telephone)
        {
            var personElement = new XElement(
                    elementName,
                    new XElement(ElementNamePersonFirstname, firstname),
                    new XElement(ElementNamePersonSurname, surname),
                    new XElement(ElementNamePersonJobtitle, jobtitle),
                    new XElement(ElementNamePersonEmail, email),
                    new XElement(ElementNamePersonTelephone, telephone)
                );
            return personElement;
        }

        /// <summary>
        /// Adds or gets element by name.
        /// </summary>
        /// <param name="elementName">Name of the element.</param>
        /// <returns></returns>
        private XElement AddOrGetElement(XName elementName)
        {
            return AddOrGetElement(_metaXml, elementName);
        }

        /// <summary>
        /// Adds or gets element by parent name and element name.
        /// </summary>
        /// <param name="mainElement">The main element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <returns></returns>
        private XElement AddOrGetElement(XElement mainElement, XName elementName)
        {
            XElement element = mainElement.Element(elementName);

            if (element == null)
            {
                element = new XElement(elementName);
                mainElement.Add(element);
            }
            return element;
        }

        /// <summary>
        /// Generic method to add or update element in the metaxml using parent and element name.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="mainElement">The main element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="elementValue">The element value.</param>
        /// <returns></returns>
        public XElement AddOrUpdateElement<T>(XElement mainElement, XName elementName, T elementValue)
        {
            XElement element = mainElement.Element(elementName);

            if (element == null)
            {
                element = new XElement(elementName, elementValue);
                mainElement.Add(element);
            }
            else
            {
                element.SetValue(elementValue);
            }
            return element;
        }

        /// <summary>
        /// Creates element name with metaxml namespace.
        /// </summary>
        /// <param name="elementName">Name of the element.</param>
        /// <returns></returns>
        public static XName GetName(string elementName)
        {
            return XName.Get(elementName, MetaXmlNamespace);
        }

        /// <summary>
        /// Gets author element.
        /// </summary>
        /// <returns></returns>
        public XElement GetAuthor()
        {
            return _metaXml.Element(ElementNameAuthor);
        }

        /// <summary>
        /// Gets the letter metadata element.
        /// </summary>
        /// <returns></returns>
        public XElement GetLetterMetadata()
        {
            return _metaXml.Element(ElementNameLetterMetadata);
        }

        /// <summary>
        /// Generic method to get element by name.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        public XElement GetElement(XName name)
        {
            return (from element in _metaXml.Descendants(name) select element).Single();
        }

        /// <summary>
        /// Gets compilators.
        /// </summary>
        /// <returns></returns>
        public List<XElement> GetCompilators()
        {
            return (from element in _metaXml.Element(ElementNameCompilators).Elements(ElementNameCompilator)
                    select element).ToList();
        }

        /// <summary>
        /// Gets signatures.
        /// </summary>
        /// <returns></returns>
        public List<XElement> GetSignatures()
        {
            return (from element in _metaXml.Element(ElementNameSignatures).Elements(ElementNameSignature)
                    select element).ToList();
        }

        /// <summary>
        /// Gets addressees.
        /// </summary>
        /// <returns></returns>
        public List<XElement> GetAddressees()
        {
            return (from element in _metaXml.Element(ElementNameAddressees).Elements(ElementNameAddressee)
                    select element).ToList();
        }

        /// <summary>
        /// Gets access rights.
        /// </summary>
        /// <returns></returns>
        public XElement GetAccessRights()
        {
            return _metaXml.Element(ElementNameLetterMetadata).Element(ElementNameLetterMetadataAccessRights);
        }

        /// <summary>
        /// Gets intellectgual property rights.
        /// </summary>
        /// <returns></returns>
        public XElement GetIntellectgualPropertyRights()
        {
            return _metaXml.Element(ElementNameLetterMetadata).Element(ElementNameLetterMetadataIntellectualPropertyRights);
        }

        /// <summary>
        /// Gets enclosures.
        /// </summary>
        /// <returns></returns>
        public List<XElement> GetEnclosures()
        {
            return (from element in _metaXml.Element(ElementNameLetterMetadata).Element(ElementNameLetterMetadataEnclosures).Elements(ElementNameLetterMetadataEnclosureTitle)
                    select element).ToList();
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// 
        /// Turn MetaXmlPart to Xml string.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return _metaXml.ToString();
        }
    }
}